package newcrunch.grader;

import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import newcrunch.gui.multispantable.AttributiveCellTableModel;
import newcrunch.gui.multispantable.ColoredCell;
import newcrunch.gui.multispantable.DefaultCellAttribute;
import newcrunch.util.Logging;
import newcrunch.util.Misc;
import newcrunch.util.PropertyLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Static class for transforming the marking scheme from xml to table model.
 * @author ccadm
 */
public class MarkingScheme
{
    private int columns;
    private int rows;
    private Vector<String> scores;
    public static Color background = PropertyLoader.getColorProperty("categoryColor");
    public static Color foregroundNormal = PropertyLoader.getColorProperty("marksNormal");
    public static Color foregroundError = PropertyLoader.getColorProperty("marksError");
    
    private MarkingScheme()
    {
        scores = new Vector<String>();
    }
    
    public static AttributiveCellTableModel generateModel(String markingScheme, boolean displayTotal) throws ParserConfigurationException, SAXException, IOException
    {
        MarkingScheme ms = new MarkingScheme();
        
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(new ByteArrayInputStream(markingScheme.getBytes()));
        Element root = doc.getDocumentElement();
        ms.columns = getRowsAndCols(root, 1, ms);
        AttributiveCellTableModel model = new AttributiveCellTableModel(ms.rows + 1, ms.columns + (displayTotal ? 1 : 0));
        initModel(model, root, 0, 0, ms);
        
        //add the scores row
        int row = model.getRowCount() - 2;
        for (int i = 0; i < ms.scores.size(); i++)
            model.setValueAt(ms.scores.elementAt(i), row, i);
        
        if (displayTotal)
        {
            //sum up the scores
            
            double total = 0;
            for (int i = 0; i < ms.scores.size(); i++)
            {
                try {
                    String scoreString = ms.scores.elementAt(i).toString();
                    //get rid of the % sign
                    total += Double.parseDouble(scoreString.substring(0, scoreString.length() - 1));
                } 
                catch (NumberFormatException e) {
                    Logging.log(e);
                }
            }
            
            int col = model.getColumnCount() - 1;
            
            //set the category name
            model.setValueAt("Total", 0, col);
            
            //set the total score
            model.setValueAt(Misc.DECIMAL_FORMAT.format(total) + "%", row, col);
            
            //combine the cells
            DefaultCellAttribute attrib = (DefaultCellAttribute)(model.getCellAttribute());
            attrib.combine(genCols(0, row), genCols(col, 1));
        }
        
        //set the background color of the categories
        ColoredCell coloredCell = (ColoredCell)model.getCellAttribute();
        for (int i = 0; i < model.getRowCount() - 1; i++)
            for (int j = 0; j < model.getColumnCount(); j++)
                coloredCell.setBackground(background, i, j);
                
        
        return model;
    }
    
    private static int initModel(AttributiveCellTableModel model, Element root, int startRow, int startCol, MarkingScheme ms)
    {
        NodeList list = root.getChildNodes();
        int childCount = 0;
        String name = "";
        String score = "";
        for (int i = 0; i < list.getLength(); i++)
            if (list.item(i) instanceof Element)
            {
                Element element = (Element)(list.item(i));
                String tagName = element.getTagName().toLowerCase();
                if ("category".equals(tagName))
                    childCount += initModel(model, (Element)(list.item(i)), startRow + 1, startCol + childCount, ms);
                
                else if ("name".equals(tagName))
                    name = element.getFirstChild().getNodeValue();
                
                else if ("score".equals(tagName))
                    score = element.getFirstChild().getNodeValue() + "%";
            }
        
        //sets the display value (-1 to remove root's row)
        if (startRow > 0)
            model.setValueAt(name, startRow - 1, startCol);
        
        DefaultCellAttribute attrib = (DefaultCellAttribute)(model.getCellAttribute());
        
        if (childCount > 0)
        {
            //combine horizontally
            attrib.combine(genCols(startRow - 1, 1), genCols(startCol, childCount));
        }
        
        
        else
        {
            //combine vertically
            attrib.combine(genCols(startRow - 1, ms.rows - startRow), genCols(startCol, 1));
            
            //only tracks the scores for leaf categories
            ms.scores.add(score);
        }
        
        
        return childCount == 0 ? 1 : childCount;
    }
    
    private static int[] genCols(int start, int len)
    {
        int[] result = new int[len];
        
        for (int i = 0; i < len; i++)
            result[i] = start + i;
        
        return result;
    }
    
    //returns the number of columns
    private static int getRowsAndCols(Element root, int depth, MarkingScheme ms)
    {
        ms.rows = Math.max(ms.rows, depth);
        NodeList list = root.getChildNodes();
        int childCount = 0;
        
        for (int i = 0; i < list.getLength(); i++)
            if (list.item(i) instanceof Element &&
                    "category".equals(((Element)(list.item(i))).getTagName().toLowerCase()))
                childCount += getRowsAndCols((Element)(list.item(i)), depth + 1, ms);
        
        return childCount == 0 ? 1 : childCount;
    }
}
